/**
 *
 */
package com.vivi.codereview.actors;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import com.vivi.codereview.WhatTheFuckException;
import com.vivi.codereview.tools.Log;
import com.vivi.codereview.tools.SystemHandler;

/**
 * The basic Developer actor, where we check checkouts, hijacks to help the user
 * creating the zip, and letting user add other files. The zip will be created
 * at the root of the selected stream.
 *
 * @author nvillard
 *
 */
public class DeveloperActor extends BaseActor {

	/**
	 * Constructor.
	 */
	public DeveloperActor() {
		super();
	}

	@Override
	public int process() throws Exception {
		Log.logSeparator();
		Set<String> chosenFilenames = new TreeSet<String>();
		// We look for jdevcc folder
		String jdevccFolder = getRootDirectory();
		// we get the folder (stream) we want to use
		String rootStream = selectFolder(jdevccFolder,
		        "Please select index of stream you want to use in list above (mandatory step):");
		// we retrieve all checkouted files for this stream
		// we sort the checkouts and group them by PTR number, meaning more
		// than one activity can be used for one PTR
		Map<String, Set<String>> filesPerActivity = getAndSortCheckoutsPerPtr(rootStream);
		// we ask the PTR number to be considered
		Set<String> checkoutedFiles = new TreeSet<String>();
		String activity = "";
		// If we have some checkouts...
		if (!filesPerActivity.keySet().isEmpty()) {
			activity = getChosenPtr(filesPerActivity.keySet());
			// we get the related filenames, plus some files the user wants to
			// add
			// In case we didn't select a ptr number, we don't do the checkouted
			// files process
			if (activity != null) {
				Log.log("we have a ptr number, we consider checkouted files", Log.D);
				checkoutedFiles = filesPerActivity.get(activity);
				addCheckoutFilesForGivenPtr(activity, checkoutedFiles, chosenFilenames);
			}
		}
		addModifiedFiles(rootStream, checkoutedFiles, chosenFilenames);
		addSomeFiles(rootStream, chosenFilenames);
		// we create a zip file with selected files
		if (checkToBeZipped(chosenFilenames)) {
		  if (checkJunitInFiles(chosenFilenames)) {
			getZipUtil().createZipFile(rootStream, activity, chosenFilenames,
			        getSysUtil().getFullFilename(rootStream, ZIP_FILENAME));
		  }
		  else {
		    Log.log("No JUnit found in project, zip file not generated!!!", Log.W);
        throw new WhatTheFuckException(Log.Exit.NO_JUNIT_EXIT);
		  }
		}
		return Log.Exit.OK;
	}

	/**
	 * Look for all checkouted files regarding the stream, sort them regarding
	 * the activity (PTR number).
	 *
	 * @param stream
	 *            the root of the stream
	 * @return Map with activity as key, and Set of files (absolute path) as
	 *         value
	 * @throws Exception
	 */
	protected Map<String, Set<String>> getAndSortCheckoutsPerPtr(String stream) throws Exception {
		Map<String, Set<String>> sortedFiles = new HashMap<String, Set<String>>();
		List<String> files = getCcUtil().getCheckoutedFiles(stream);
		if (!files.isEmpty()) {
			sortedFiles = getCcUtil().sortCheckoutsPerPtr(files);
		} else {
			Log.log("no checkout", Log.W);
		}
		return sortedFiles;
	}

	/**
	 * Let the user chosen meanwhile checkouted files for a given activity which
	 * ones he wants to add in the zip.
	 *
	 * @param activity
	 *            the activity we selected
	 * @param checkoutedFiles
	 *            All checkouted files related to this activity
	 * @param selectedFiles
	 *            has to be not null, where selected files are added
	 * @throws Exception
	 */
	protected void addCheckoutFilesForGivenPtr(String activity, Set<String> checkoutedFiles, Set<String> selectedFiles)
	        throws Exception {
		if (!checkoutedFiles.isEmpty()) {
			Log.log("***** CHECKOUTED FILES *****", Log.I);
			Set<String> checkoutedSelectedFiles = getInputUtil().letUserChooseMany(checkoutedFiles,
			        String.CASE_INSENSITIVE_ORDER, getSelectRangeUtil().getSelectFilesDisplayedText());
			selectedFiles.addAll(checkoutedSelectedFiles);
		} else {
			Log.log("no checkout found", Log.W);
		}
	}

	/**
	 * to get list of modified files and ask user to select the ones we want to
	 * consider. We have some filtering to remove useless files (not source
	 * file, already checkouted...).
	 *
	 * @param stream
	 *            the stream we parse
	 * @param alreadyCheckoutedFiles
	 *            Set of already checkouted files, to remove them from the list
	 *            and don't display them twice (better than calling twice
	 *            cleartool)
	 * @param selectedFiles
	 *            Has to be not null, where selected files are added
	 */
	protected void addModifiedFiles(String stream, Set<String> alreadyCheckoutedFiles, Set<String> selectedFiles)
	        throws Exception {
		// We retrieve all modified files (not Read-Only)
		Log.log("We are looking for modified files. Can take some time", Log.I);
		List<String> outputs = getSysUtil().launchCommandAndGetOutput(stream, MODIFIED_FILES_COMMAND);
		Set<String> modifiedFiles = new TreeSet<String>();
		for (String output : outputs) {
			// We check is a considered file and not already in checkouted
			// files
			if (output.matches(SystemHandler.FILE_REGEXP) && isValidModifiedFile(output)) {
				if (alreadyCheckoutedFiles != null && !alreadyCheckoutedFiles.contains(output)) {
					Log.log(output + " is added in list of hijacked files", Log.D);
					modifiedFiles.add(output);
				} else {
					Log.log(output + " is already considered as checkouted", Log.D);
				}
			} else {
				Log.log(output + " is not a valid hijacked file", Log.DD);
			}
		}
		if (!modifiedFiles.isEmpty()) {
			Log.log("***** MODIFIED FILES *****", Log.I);
			Set<String> modifiedSelectedFiles = getInputUtil().letUserChooseMany(modifiedFiles,
			        String.CASE_INSENSITIVE_ORDER, getSelectRangeUtil().getSelectFilesDisplayedText());
			selectedFiles.addAll(modifiedSelectedFiles);
		} else {
			Log.log("no modified file found", Log.W);
		}
	}

	/**
	 * We do some filterings to ensure we don't display too many hijacked files.
	 * We filter out pom files, generated files, eclipse files, and some very
	 * specific files.
	 *
	 * @param hijackedFile the current hijacked file we check
	 * @return true if is valid hijack, to be proposed to the end user
	 */
	protected boolean isValidModifiedFile(String hijackedFile) throws Exception {
		return !hijackedFile.contains("pom.xml") && !hijackedFile.contains("target") && !hijackedFile.contains(".metadata")
		        && !hijackedFile.contains("full_JCP_Config.xml") && !hijackedFile.contains("generated")
		        && !hijackedFile.contains("objectModelXMI.xml");
	}

	/**
	 * Let the user adding any file he wants, checking only the stream is the same.
	 *
	 * @param selectedFiles Has to be not null, where the selected files will be added
	 * @throws Exception
	 */
	protected void addSomeFiles(String root, Set<String> selectedFiles) throws Exception {
		boolean doWeLoop = true;
		while (doWeLoop) {
			Log.log("***** OTHER FILES *****", Log.I);
			Log.logSeparator();
			String input = getInputUtil()
			        .getUserInput(
			                "Maybe you want to add some files not listed (not yet added in SourceControl for example). You can add them giving the full path one by one\n(ex: 'G:\\jdevcc\\jcp_v17.0_be_int\\be1111_core\\be\\hotel\\hotel-avail\\action\\src\\main\\java\\com\\amadeus\\jcp\\standard\\action\\hotel\\HotelRetrieveContentAction.java')\n<Enter> to stop:");
			if (getSelectRangeUtil().isNullSelectRange(input)) {
				Log.log("Since user entered nothing, we stop addSomeFile process", Log.D);
				doWeLoop = false;
			} else {
				if (input.startsWith(root)) {
					Log.log("we add this file " + input, Log.D);
					selectedFiles.add(input);
				} else {
					Log.log("This file is not in the stream directory " + input + " not in " + root, Log.W);
				}
			}
		}
	}

	/**
	 * We ask the user to select which PTR we wants to use.
	 *
	 * @param ptrs available PTRs
	 * @return the selected one, can be nul
	 * @throws Exception
	 */
	protected String getChosenPtr(Set<String> ptrs) throws Exception {
		if (!ptrs.isEmpty()) {
			return getInputUtil().letUserChooseOne(ptrs,
			        "Please select index of PTR you want to use in list above.\n<Enter> if no PTR number:", false);
		} else {
			Log.log("no ptr found", Log.W);
			return null;
		}
	}

	@Override
	public String getName() {
		return "Manual Developer";
	}

}
